package cn.elead.chaos.framework.web.enums;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.core.annotation.AnnotationUtils;

import com.fasterxml.jackson.annotation.JsonValue;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;

public class EnumFactory {

	private EnumFactory() {
	}

	/** 按照类来缓存已经加载的所有的Enum **/
	@SuppressWarnings("rawtypes")
	public static ConcurrentHashMap<Class, Set<IEnum>> enumCache = new ConcurrentHashMap<Class, Set<IEnum>>();

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void addAll(Class enumClazz) {
		if (enumClazz == null) {
			return;
		}
		init(enumClazz);
		Set set = enumCache.get(enumClazz);
		if (set == null) {
			set = new HashSet<IEnum>();
			enumCache.putIfAbsent(enumClazz, set);
		}
		set = enumCache.get(enumClazz);
		set.add(enumClazz);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <R extends IEnum<T>, T> void add(R enumValue) {
		if (enumValue == null) {
			return;
		}
		Class<R> enumClass = (Class<R>) enumValue.getClass();
		Set set = enumCache.get(enumClass);
		if (set == null) {
			set = new HashSet<IEnum<T>>();
			enumCache.putIfAbsent(enumClass, set);
		}
		set = enumCache.get(enumClass);
		set.add(enumValue);
	}

	/**
	 * 根据value获取enum
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <R extends IEnum<T>, T> R getByValue(Class<R> enumClass, T value, String source) {
		if (enumClass == null) {
			return null;
		}
		if (NumberUtil.isInteger(source)) {
			R[] enumConstants = enumClass.getEnumConstants();
			for (R en : enumConstants) {
				if (ObjectUtil.equal(String.valueOf(en.getValue()), source)) {
					return en;
				}
			}
		}
		init(enumClass);
		Optional<Method> dealEnumMethod = dealEnumMethod(enumClass);
		//Optional<Field> dealEnumField = dealEnumField(enumClass);
		Set<IEnum> set = enumCache.get(enumClass);
		for (IEnum<T> e : set) {
			if (e.getValue().equals(value)) {
				return (R) e;
			} else if (dealEnumMethod.isPresent()) {
				Object invoke = ReflectUtil.invoke(e, dealEnumMethod.get(), null);
				if (ObjectUtil.equal(invoke, value)) {
					return (R) e;
				}
			} /**else if(dealEnumField.isPresent()) {
				Object fieldValue = ReflectionKit.getMethodValue(e, dealEnumField.get().getName());
				if (ObjectUtil.equal(fieldValue, value)) {
					return (R) e;
				}
			}**/
		}
		return null;
	}

	/**
	 * 存在@JsonValue注解的方法
	 * 
	 * @param clazz
	 * @return
	 */
	public static Optional<Method> dealEnumMethod(Class<?> clazz) {
		return clazz.isEnum()
				? Arrays.stream(clazz.getMethods())
						.filter(method -> ObjectUtil.isNotNull(AnnotationUtils.findAnnotation(method, JsonValue.class)))
						.findFirst()
				: Optional.empty();
	}

	/**
	 * 存在@JsonValue注解属性字段
	 * 
	 * @param clazz
	 * @return
	 */
	public static Optional<Field> dealEnumField(Class<?> clazz) {
		return clazz.isEnum()
				? Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(JsonValue.class))
						.findFirst()
				: Optional.empty();
	}

	/**
	 * 存在@JsonValue注解的方法
	 * 
	 * @param clazz
	 * @return
	 */
	public static Optional<Method> dealEnumType(Class<?> clazz) {
		return clazz.isEnum()
				? Arrays.stream(clazz.getMethods())
						.filter(method -> ObjectUtil.isNotNull(AnnotationUtils.findAnnotation(method, JsonValue.class)))
						.findFirst()
				: Optional.empty();
	}

	/**
	 * 根据desc获取enum
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <R extends IEnum<T>, T> R getByDesc(Class<R> enumClass, String name) {
		if (enumClass == null) {
			return null;
		}
		init(enumClass);
		Set<IEnum> set = enumCache.get(enumClass);
		for (IEnum<T> e : set) {
			if (e.getName().equals(name)) {
				return (R) e;
			}
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public static <R extends IEnum<V>, V> List<R> getAll(Class<R> clazz) {
		if (clazz == null) {
			return null;
		}
		init(clazz);
		Set<R> set = (Set<R>) enumCache.get(clazz);
		if (set != null && set.size() > 0) {
			List<R> list = new ArrayList<R>(set.size());
			list.addAll(set);
			return list;
		}
		return null;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <R extends IEnum<V>, V> List<Class<R>> getAllEnumClass() {
		List<Class<R>> list = new ArrayList<Class<R>>();
		Enumeration<Class> keys = enumCache.keys();
		while (keys.hasMoreElements()) {
			Class clazz = keys.nextElement();
			list.add(clazz);
		}
		return list;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <V, R extends IEnum<V>> Class<R> getEnumTypeByAlias(String aliasClassName) {
		if (aliasClassName == null || aliasClassName.length() <= 0) {
			return null;
		}
		Set<Map.Entry<Class, Set<IEnum>>> entrySet = enumCache.entrySet();
		for (Map.Entry<Class, Set<IEnum>> entry : entrySet) {
			Class clazz = entry.getKey();
			if (clazz.getSimpleName().equalsIgnoreCase(aliasClassName)) {
				return clazz;
			}
		}
		return null;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <V, R extends IEnum<V>> Class<V> getValueTypeByClass(Class<R> enumClsss) {
		if (enumClsss == null) {
			return null;
		}

		Set<IEnum> set = enumCache.get(enumClsss);

		Method[] methods = enumClsss.getMethods();
		for (Method method : methods) {
			JsonValue jsonValue = AnnotationUtils.findAnnotation(method, JsonValue.class);
			if (ObjectUtil.isNotNull(jsonValue)) {
				return (Class<V>) method.getAnnotatedReturnType().getType();
			}
		}
		return (Class<V>) set.iterator().next().getValue().getClass();
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <T extends IEnum> T convertByStringValue(Class<T> enumClass, String source) {
		if (enumClass == null || source == null || source.length() <= 0) {
			return null;
		}
		Class valueClazz = EnumFactory.getValueTypeByClass(enumClass);
		if (valueClazz == String.class || valueClazz == Character.class || valueClazz == char.class) {
			return (T) EnumFactory.getByValue(enumClass, source, source);
		} else if (valueClazz == Boolean.class || valueClazz == boolean.class) {
			return (T) EnumFactory.getByValue(enumClass, Boolean.valueOf(source), source);
		} else if (valueClazz == Integer.class || valueClazz == int.class) {
			return (T) EnumFactory.getByValue(enumClass, Integer.valueOf(source), source);
		} else if (valueClazz == Long.class || valueClazz == long.class) {
			return (T) EnumFactory.getByValue(enumClass, Long.valueOf(source), source);
		} else if (valueClazz == Double.class || valueClazz == double.class) {
			return (T) EnumFactory.getByValue(enumClass, Double.valueOf(source), source);
		} else if (valueClazz == Float.class || valueClazz == float.class) {
			return (T) EnumFactory.getByValue(enumClass, Float.valueOf(source), source);
		} else if (valueClazz == Byte.class || valueClazz == byte.class) {
			return (T) EnumFactory.getByValue(enumClass, Byte.valueOf(source), source);
		} else if (valueClazz == Short.class || valueClazz == short.class) {
			return (T) EnumFactory.getByValue(enumClass, Short.valueOf(source), source);
		} else {
			return (T) EnumFactory.getByValue(enumClass, source, source);
		}
	}

	private static <R> void init(Class<R> enumClass) {
		// 说明已经初始化过了
		if (enumCache.get(enumClass) != null) {
			return;
		}
		// 反射获取其中的一个field，导致类实例化，导致所有的static的Field初始化，然后就会被添加到enumCache中
		Field[] fields = enumClass.getFields();
		if (fields.length <= 0) {
			return;
		}
		Field field = fields[0];
		try {
			field.setAccessible(true);
			field.get(null);
		} catch (Throwable e) {
			//
		}
	}

}
